package network;

import gui.ConsoleResoUI;
import gui.Forme;

import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.MalformedURLException;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import output.OutTrace;

public class Reso extends UnicastRemoteObject implements IReso {

	private final String address = "127.0.0.1";
	private final int electionType = 1;

	private static final long serialVersionUID = 1L;
	private final int maxProcess = 10;
	private Map<Integer, IProcessus> listOfProcess;
	private int lastIdProcess;
	private ConsoleResoUI console;
	private final Random rand = new Random();
	public OutTrace outTrace;

	public Reso() throws RemoteException {
		super();
		//Trouver l'adresse:

		String myAddress="";

		// Recupere l'adresse adaptee
		Enumeration<NetworkInterface> en;
		try {
			en = NetworkInterface.getNetworkInterfaces();

			while (en.hasMoreElements()){
			   List<InterfaceAddress> i = en.nextElement().getInterfaceAddresses();

			  for (InterfaceAddress l : i) {
			    InetAddress addr = l.getAddress();

			    if (addr.isSiteLocalAddress()){
			      myAddress=addr.getHostAddress();
			     }
			   }
			}
		} catch (SocketException e) {
			e.printStackTrace();
		}

		// Ensuite positionner l'adresse qui convient :

		System.setProperty("java.rmi.server.hostname", myAddress);
		this.lastIdProcess = maxProcess - 1;
		this.setListOfProcess(new HashMap<Integer, IProcessus>(maxProcess));
		console = new ConsoleResoUI(this);
		this.outTrace = new OutTrace("out_Reso");
	}

	public void start() throws MalformedURLException {

		try {
			LocateRegistry.createRegistry(1099);
			Naming.rebind("Reso", Reso.this);
			System.out.println("Reso : Server available");
			outTrace.ecrire("Reso : Server available");
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}

	public void addProcessus() {
		new Thread(new Runnable() {
			public void run() {

				Processus p = new Processus(Reso.this.address, electionType);
				p.start();
			}
		}).start();
	}

	private void affiche(String s) {
		this.console.affiche(s);
	}

	private final void waitAsync() {

		try {
			Thread.sleep(250 + rand.nextInt(250));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public ArrayList<Integer> getListOfProcess() throws RemoteException {

		return new ArrayList<Integer>(listOfProcess.keySet());
	}

	public void setListOfProcess(Map<Integer, IProcessus> listOfProcess) {
		this.listOfProcess = listOfProcess;
	}

	public synchronized int requestJoinGroup() throws RemoteException {

		if (this.lastIdProcess >= 0) {
			this.lastIdProcess--;
			return lastIdProcess + 1;
		} else {
			return -1;
		}
	}

	public synchronized void requestLeaveGroup(int id) throws RemoteException {

		this.listOfProcess.remove(id);
	}

	public synchronized void registerProcessus(int id) throws RemoteException {

		System.out.println("Reso : Register client " + id + " ... ");
		outTrace.ecrire("Reso : Register client " + id + " ... ");
		this.affiche("Reso : Register client " + id + " ... ");
		IProcessus remoteProcessus = null;

		try {
			Registry registry = LocateRegistry.getRegistry(1099);
			remoteProcessus = (IProcessus) registry.lookup("pid" + id);
			this.listOfProcess.put(id, remoteProcessus);
		} catch (NotBoundException e) {
			e.printStackTrace();
		}

		// System.out.println("id recupere depuis le client : " +
		// remoteProcessus.getId());
	}

	@Override
	public void sendRequestListOfFormeReso(final int idSource, final int idDest)
			throws RemoteException {

		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						Reso.this.listOfProcess.get(idDest)
								.receiveRequestListOfForme(idSource);
					} else {
						Reso.this.listOfProcess.get(idSource).timeout(idDest);
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	@Override
	public void sendListOfFormeReso(final int idSource, final int idDest,
			final Map<Integer, Forme> listOfForme) throws RemoteException {

		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						System.out
								.println("Reso : sendListOfFormeReso idsource = "
										+ idSource + ", iddest = " + idDest);
						outTrace.ecrire("Reso : sendListOfFormeReso idsource = "
								+ idSource + ", iddest = " + idDest);
						Reso.this
								.affiche("Reso : sendListOfFormeReso idsource = "
										+ idSource + ", iddest = " + idDest);
						Reso.this.listOfProcess.get(idDest).receiveListOfForme(
								idSource, listOfForme);
					} else {
						Reso.this.listOfProcess.get(idSource).timeout(idDest);
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	public synchronized void searchMaster(final int idSource, final int idDest)
			throws RemoteException {

		// System.out.println("Reso : searchMaster idsource = " + idSource +
		// ", iddest = "+ idDest);
		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						System.out.println("Reso : searchMaster idsource = "
								+ idSource + ", iddest = " + idDest
								+ "   --- PAS NULL");
						outTrace.ecrire("Reso : searchMaster idsource = "
								+ idSource + ", iddest = " + idDest
								+ "   --- PAS NULL");
						Reso.this.listOfProcess.get(idDest).addProcessus(
								idSource);
					} else {
						Reso.this.listOfProcess.get(idSource).timeout(idDest);
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	@Override
	public void responseMasterReso(final int idSource, final int idDest)
			throws RemoteException {

		System.out.println("Reso : responseMasterReso idsource = " + idSource
				+ ", idDest = " + idDest);
		outTrace.ecrire("Reso : responseMasterReso idsource = " + idSource
				+ ", idDest = " + idDest);
		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						Reso.this.listOfProcess.get(idDest)
								.responseSearchMaster(idSource);
					} else {
						Reso.this.listOfProcess.get(idSource).timeout(idDest);
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	public synchronized void sendDrawingRequestReso(final int idSource,
			final int idDest) throws RemoteException {

		System.out.println("Reso : sendDrawingRequestReso idsource = "
				+ idSource + ", idDest = " + idDest);
		outTrace.ecrire("Reso : sendDrawingRequestReso idsource = " + idSource
				+ ", idDest = " + idDest);
		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						Reso.this.listOfProcess.get(idDest)
								.receiveDrawingRequestMaster(idSource);
					} else {
						Reso.this.listOfProcess.get(idSource).timeout(idDest);
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	@Override
	public void receiveDrawingResponseReso(final int idDest,
			final boolean scAvailable) throws RemoteException {

		System.out.println("Reso : receiveDrawingResponseReso idDest = "
				+ idDest);
		outTrace.ecrire("Reso : receiveDrawingResponseReso idDest = " + idDest);
		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						Reso.this.listOfProcess.get(idDest)
								.receiveDrawingResponse(idDest, scAvailable);
					} else {
						// Cas impossible dans le cahier des charges
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	public void sendFormeReso(final int idSource, final int idDest,
			final int idForme, final Forme forme) throws RemoteException {

		new Thread(new Runnable() {
			public void run() {
				waitAsync();
				try {
					if (Reso.this.listOfProcess.get(idDest) != null) {
						Reso.this.listOfProcess.get(idDest).receiveForme(
								idSource, idForme, forme);
					} else {
						Reso.this.listOfProcess.get(idSource).timeout(idDest);
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	@Override
	public void sendAckForme(final int idSource, final int idDest)
			throws RemoteException {

		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						Reso.this.listOfProcess.get(idDest).receiveAckForme(
								idSource);
					} else {
						// Cas impossible dans le cahier des charges
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	@Override
	public void sendEndSC(final int idSource, final int idDest)
			throws RemoteException {

		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						Reso.this.listOfProcess.get(idDest).receiveEndOfSC(
								idSource);
					} else {
						// Cas impossible dans le cahier des charges
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	@Override
	public void sendMsgElection(final int idSource, final int idDest)
			throws RemoteException {

		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						Reso.this.listOfProcess.get(idDest).msgElection(
								idSource);
					} else {
						Reso.this.listOfProcess.get(idSource).timeout(idDest);
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	@Override
	public void sendMsgAck(final int idSource, final int idDest)
			throws RemoteException {

		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						Reso.this.listOfProcess.get(idDest).msgAck();
					} else {
						// Cas impossible dans le cahier des charges
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	@Override
	public void sendMsgMaster(final int idSource, final int idDest,
			final int master) throws RemoteException {

		new Thread(new Runnable() {
			public void run() {
				try {
					waitAsync();
					if (Reso.this.listOfProcess.get(idDest) != null) {
						Reso.this.listOfProcess.get(idDest).msgMaster(master);
					} else {
						// Cas impossible dans le cahier des charges
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	/** PARTIE CHANG & ROBERTS ********************************************************/

	public void sendTok(final int idSource, final int val, final int idDest)
			throws RemoteException {

		new Thread(new Runnable() {
			public void run() {
				if (Reso.this.listOfProcess.get(idDest) == null) {
					try {
						Reso.this.listOfProcess.get(idSource).timeoutCR(idDest);
					} catch (RemoteException e) {
						e.printStackTrace();
					}
				} else {
					System.out.println("Reso : " + idSource + " send <tok, "
							+ val + "> to " + idDest);
					outTrace.ecrire("Reso : " + idSource + " send <tok, " + val
							+ "> to " + idDest);
					try {
						Reso.this.listOfProcess.get(idDest).getTokv2(val);
					} catch (RemoteException e) {
						System.out.println("Reso : Process " + idDest
								+ " is over");
						outTrace.ecrire("Reso : Process " + idDest + " is over");
						// e.printStackTrace();
					}
				}
			}
		}).start();
	}

	public void sendLeader(final int idLeader, final int voisin)
			throws RemoteException {
		new Thread(new Runnable() {
			public void run() {
				System.out.println("Reso : Process says " + idLeader
						+ " is the master to " + voisin);
				outTrace.ecrire("Reso : Process says " + idLeader
						+ " is the master to " + voisin);
				try {
					Reso.this.listOfProcess.get(voisin).receiveLeader(idLeader);
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}).start();

	}

	/** FIN PARTIE CHANG & ROBERTS ****************************************************/

	public static void main(String args[]) {
		Reso reso = null;

		try {
			reso = new Reso();
			reso.start();
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}

}
